﻿using System;
using System.Collections;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Text.RegularExpressions;
using Affinity.Core.Messages;
using Affinity.Flux.Pairings;

namespace Affinity.Flux
{
    /// <summary>
    /// A matchmaker implementation providing tracing functionality: each time data or execution flows from an outlet to an inlet,
    /// Debug.WriteLine is called with information about that flow.
    /// </summary>
    public class TracingMatchMaker : StandardMatchMaker
    {
        /// <summary>
        /// Constructor taking a Regex filter to apply to contract names (or null to not filter).
        /// </summary>
        /// <param name="filter"></param>
        public TracingMatchMaker(Regex filter)
        {
            _Filter = filter;
        }

        /// <summary>
        /// Creates a pairing for the given outlet and inlet.
        /// </summary>
        /// <param name="outlet"></param>
        /// <param name="inlet"></param>
        /// <returns></returns>
        protected override Pairing CreatePairing(Outlet outlet, Inlet inlet)
        {
            Pairing pairing = null;

            // In: Method, Out: Event

            if (inlet.Member is MethodInfo && outlet.Member is EventInfo)
            {
                var eventMethodPairing = new EventMethodPairing(outlet, inlet, true);
                eventMethodPairing.ExecutionPushed += HandleEventMethodPairingExecutionPushed;
                pairing = eventMethodPairing;
            }

            // In: Property, Out: Property

            if (inlet.Member is PropertyInfo && outlet.Member is PropertyInfo)
            {
                var propertyPropertyPairing = new PropertyPropertyPairing(outlet, inlet);
                propertyPropertyPairing.ValuePushed += HandlePropertyPropertyPairingValuePushed;
                pairing = propertyPropertyPairing;
            }

            //TODO? In: Method, Out: Delegate

            //TODO? In: Property, Out: Type

            if (pairing == null) throw new NotImplementedException();

            return pairing;
        }

        /// <summary>
        /// Reacts to the transfer of a value from a property outlet to a property inlet.
        /// </summary>
        private void HandlePropertyPropertyPairingValuePushed(PropertyPropertyPairing propertyPropertyPairing, object newValue, TimeSpan duration)
        {
            DateTime beginTime = DateTime.Now;

            if (_Filter == null || _Filter.IsMatch(propertyPropertyPairing.Contract.ToString()))
            {
                Debug.WriteLine("[{0}] Property ({1} -> {2}) Value: {3} ({4} ms)", 
                    propertyPropertyPairing.Contract, 
                    propertyPropertyPairing.Outlet.ToShortString(), 
                    propertyPropertyPairing.Inlet.ToShortString(),
                    FormatValue(newValue), 
                    duration.TotalMilliseconds);
            }
        }

        /// <summary>
        /// Reacts to the transfer of execution from an event outlet to a method inlet.
        /// </summary>
        private void HandleEventMethodPairingExecutionPushed(EventMethodPairing eventMethodPairing, object[] parameters, TimeSpan duration)
        {
            // Ignoring log events.
            if (!(parameters.FirstOrDefault() is LogMessage) && (_Filter == null || _Filter.IsMatch(eventMethodPairing.Contract.ToString())))
            {
                Debug.WriteLine("[{0}] Event ({1} -> {2}) Arguments: {3}  ({4} ms)", 
                    eventMethodPairing.Outlet.Contract, 
                    eventMethodPairing.Outlet.ToShortString(),
                    eventMethodPairing.Inlet.ToShortString(), string.Join(", ", parameters.Select(FormatValue)), 
                    duration.TotalMilliseconds);
            }
        }

        /// <summary>
        /// Formats a value for tracing output.
        /// </summary>
        /// <param name="value"></param>
        /// <returns></returns>
        private static string FormatValue(object value)
        {
            string formattedValue;

            if (value == null) formattedValue = "[null]";
            else if (value is IEnumerable) formattedValue = string.Format("{0} (Length: {1})", value, ((IEnumerable)value).Cast<object>().Count());
            else formattedValue = value.ToString();

            return formattedValue;
        }

        private readonly Regex _Filter;
    }
}
